﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace KingsSharp.Web.Helper
{

    /// <summary>
    /// 存放上传文件的信息
    /// </summary>
   [Serializable]
    public class UploadFileHeadData
    {
        /// <summary>
        /// 临时文件的扩展名
        /// </summary>
        public const string FILEEXT = ".config.k";

        /// <summary>
        /// 头部信息的大小
        /// </summary>
        public const int FILESIZE = 1048576;

        /// <summary>
        /// 头部标识分割符
        /// </summary>
        public const string FILESIGN = "C12BB97A-13C8-411B-ABD7-EF15E5D6F9FD";
        public UploadFileHeadData()
        {
            this.UploadEmptyChunks = new List<int>();
            this.UploadErrorChunks = new List<int>();
            this.Instance = false;
        }

       /// <summary>
       /// 是否为初始化
       /// </summary>
        public bool Instance { get; set; }

        /// <summary>
        /// 文件总大小
        /// </summary>
        public long Size { get; set; }

        /// <summary>
        /// 分片大小
        /// </summary>
        public int ChunkSize { get; set; }

        /// <summary>
        /// 总区块数
        /// </summary>
        public int Chunks { get; set; }
        /// <summary>
        /// 文件MD5
        /// </summary>
        public string Md5 { get; set; }
        /// <summary>
        /// 文件还未上传的区块
        /// </summary>
        public List<int> UploadEmptyChunks { get; set; }

        /// <summary>
        /// 文件上传时损坏（出错）的区块
        /// </summary>
        public List<int> UploadErrorChunks { get; set; }

        /// <summary>
        /// 文件其它信息
        /// </summary>
        public object Other { get; set; }
    }
    public class UploaderHelper
    {
        /// <summary>
        /// 文件上传
        /// </summary>
        /// <param name="request">请求的request</param>
        /// <param name="savepath">文件保存的路径、不提供默认为 根目录+WebFiles </param>
        /// <param name="setfilename">文件保存时的文件名委托、不提供则为上传时的文件名</param>
        /// <param name="md5check">文件MD5检测</param>
        /// <returns></returns>
        public  UploaderResult Process(HttpRequestBase request, string savepath = null
            ,Func<string,string> setfilename=null,Func<string,bool> md5check=null)
        {
            #region formData的获取、分块的检测
            //startcheckchunk参数表明用于上传前的检测、返回md5或分片验证的信息
            if (request["startcheckchunk"] != null && Convert.ToBoolean(request["startcheckchunk"]))
            {
                return ProcessCheck(request, savepath, setfilename, md5check);
            }

            UploaderResult obj = new UploaderResult();
            if (request.Files.Count == 0)
            {
                obj.Message = "请求中不包含文件流信息";
                return obj;
            }
            if (request["size"] == null)
            {
                obj.Message = "文件大小为空";
                return obj;
            }
            //文件总大小
            long size = Convert.ToInt64(request["size"]);
            //文件路径
            string path = string.IsNullOrWhiteSpace(savepath) ? AppDomain.CurrentDomain.BaseDirectory + "WebFiles\\" : savepath;
            //文件
            HttpPostedFileBase file = request.Files[0];
            //文件最终保存名称
            string savename = setfilename == null ? file.FileName : setfilename(file.FileName) == "" ? file.FileName : setfilename(file.FileName);

            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            #endregion
           
            if (request.Form.AllKeys.Any(m => m == "chunk"))//是否分片
            {
                int chunk = Convert.ToInt32(request.Form["chunk"]);//当前区块

                int chunks = Convert.ToInt32(request.Form["chunks"]);//总区块

                #region 初始化
                UploadFileHeadData headData = new UploadFileHeadData();
                if (!File.Exists(path + savename + UploadFileHeadData.FILEEXT))
                {
                    headData.Chunks = chunks;
                    headData.Size = size;
                    for (int i = 0; i < chunks-1; i++)
                    {
                        headData.UploadEmptyChunks.Add(i);
                    }
                    //并发点。假设顾客端连续post3次分片、当检测分片配置文件是否存在时，都不存在。
                    //所以这里都会被调用。
                    SetHeadData(headData, path + savename + UploadFileHeadData.FILEEXT);
                }
                else
                {
                    //并发点。当其中一次请求的post还没有写完分片数据时，此时读取的UploadFileHeadData应该是脏读的。
                    headData = GetHeadData(path + savename + UploadFileHeadData.FILEEXT);
                }
                #endregion

                #region 写区块数据
                if (chunks > 1 && chunk + 1 <= chunks)//写区块数据
                {
                    byte[] filebyte = new byte[file.ContentLength];
                    file.InputStream.Read(filebyte, 0, file.ContentLength);

                    long chunkfilesize = file.ContentLength;//分片大小等于文件大小
                    if (chunks > 1 && chunk + 1 == chunks)
                    {
                        //如果为最后一个分块 分页大小=(总大小-最后一块大小)/(总块数量-1)
                        chunkfilesize = (size - file.ContentLength) / (chunks - 1);
                    }
                    long beginsize = chunk * chunkfilesize;
                    using (FileStream fsw = new FileStream(path + savename, FileMode.OpenOrCreate,
                          FileAccess.ReadWrite, FileShare.ReadWrite, 8, true))
                    {
                       
                       // fsw.Lock(chunk * chunkfilesize, file.ContentLength);
                        fsw.Seek(beginsize, SeekOrigin.Current);

                        fsw.Write(filebyte, 0, file.ContentLength);
                        fsw.Flush(); 
                        //fsw.Close();
                    }
                    headData.UploadEmptyChunks.RemoveAll(a => a == chunk);

                    SetHeadData(headData, path + savename + UploadFileHeadData.FILEEXT);
                    
                }
                #endregion
            }
            else
            {
                file.SaveAs(path + savename);
                obj.Filish = true;
                obj.Message = "上传已完成、模式 no chunk";
            }

            #region 合并实现
            //if (!Directory.Exists(path))    //判断给定的路径上是否存在该目录
            //{
            //    Directory.CreateDirectory(path);    //不存在则创建该目录
            //}
            //string nameforsave = setfilename == null ? file.FileName : setfilename(file);//文件保存时的名称
            //obj.Name = nameforsave;
            //if (request.Form.AllKeys.Any(m => m == "chunk"))
            //{
            //    int chunk = Convert.ToInt32(request.Form["chunk"]);//当前区块

            //    int chunks = Convert.ToInt32(request.Form["chunks"]);//总区块

            //    obj.Chunk = chunk; obj.Chunks = chunks;

            //    if (chunks > 1 && chunk + 1 <= chunks)//分块文件保存
            //    {
            //        nameforsave = chunk.ToString() + "_" + file.FileName;

            //        file.SaveAs(path + nameforsave);
            //    }
            //    else if (chunks == 1)//总量为1、直接保存
            //    {
            //        obj.Filish = true;
            //        file.SaveAs(path + nameforsave);
            //    }

            //    string lastsavafile  = setfilename == null ? file.FileName : setfilename(file);

            //    if (NoWriteTempFile.Any(a => a.Key == path + lastsavafile))
            //    {
            //        using (FileStream fsw = new FileStream(path + lastsavafile, FileMode.Open, FileAccess.Write,FileShare.Write))
            //        {
            //            UploaderMerge merge = NoWriteTempFile[path + lastsavafile];
            //            string writefilename = path + chunk.ToString() + "_" + file.FileName;
            //            fsw.Seek(merge.start, SeekOrigin.Begin);
            //            fsw.Write(File.ReadAllBytes(writefilename), 0, file.ContentLength);
            //            BinaryWriter bw = new BinaryWriter(fsw);
            //            File.Delete(writefilename);
            //            NoWriteTempFileDelete(path + lastsavafile);
            //            bw.Flush(); 
            //           // bw.Write(path + chunk.ToString() + "_" + file.FileName,);
            //        }
            //    }
            //    if(chunks>1 && chunk + 1 == chunks)//最后一个区块、需合并文件
            //    {
            //        //问题结症所在、当异步提交时，可能最后一个区块先上传，那么立刻会开始合并，而前面的区块
            //        //此时并没有上传，合并时自然无法找到文件。
            //        //解决思路：首先记录一个写文件流的起始位置、写入同等大小的空字节流，然后等真正的文件
            //        //上传来之后，再打开文件进行覆写
            //        using (FileStream fsw = new FileStream(path + lastsavafile, FileMode.Create, FileAccess.Write, FileShare.Write))
            //        {
            //             BinaryWriter bw = new BinaryWriter(fsw);
            //             for (int i = 0; i < chunks; i++)//遍历区块、写入文件
            //             {
            //                 string writefilename = path + i.ToString() + "_" + file.FileName;
            //                 if (File.Exists(writefilename))
            //                 {
            //                     byte[] tempfilebyte = File.ReadAllBytes(writefilename);
            //                     bw.Write(tempfilebyte);
            //                     File.Delete(writefilename);
            //                 }
            //                 else
            //                 {
            //                     long wsize = size - file.ContentLength / (chunks - 1);
            //                     //?wsize如何取得大小成为关键
            //                     byte[] nullbyte = new byte[wsize];
            //                     UploaderHelper.NoWriteTempFileAdd(path + lastsavafile, new UploaderMerge()
            //                     {
            //                         start = fsw.Length,
            //                         end = wsize
            //                     });
            //                     bw.Write(nullbyte);
            //                 }
            //             }
            //             bw.Flush();
            //             bw.Close();
            //            //清理缓冲区
            //        }
            //    }

            //    if (chunk >= chunks)
            //    {
            //        obj.Message = "???文件已上传完成、请求还在继续、请检查";
            //    }
            //}
            //else
            //{

            //    file.SaveAs(path + nameforsave);
            //    obj.Filish = true;
            //    obj.Message = "上传已完成、模式 no chunk";
            //}
            #endregion

            return obj;
        }

        /// <summary>
        /// 设置头部配置文件
        /// </summary>
        /// <param name="headData"></param>
        /// <param name="path"></param>
        private  void SetHeadData(UploadFileHeadData headData,string path)
        {
            using (FileStream fsw = new FileStream(path, FileMode.OpenOrCreate,
                FileAccess.ReadWrite, FileShare.ReadWrite, 8, true))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                fsw.Seek(0, SeekOrigin.Begin);
                formatter.Serialize(fsw, headData);
                fsw.Flush(true); 
            }
        }

        /// <summary>
        /// 获取头部信息
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private UploadFileHeadData GetHeadData(string path)
        {
            UploadFileHeadData headData = new UploadFileHeadData();
            if (!File.Exists(path))
            {
                headData = new UploadFileHeadData() { Other = "上传还未开始", Instance = true };
                return headData;
            }
            using (FileStream fsw = new FileStream(path, FileMode.Open
                , FileAccess.ReadWrite, FileShare.ReadWrite,8,true))
            {

                BinaryFormatter formatter = new BinaryFormatter();
                fsw.Seek(0, SeekOrigin.Begin);
                headData = (UploadFileHeadData)formatter.Deserialize(fsw);
                //fsw.Flush(true);
                //fsw.Close();
            }
            return headData;
        }

        /// <summary>
        /// 断点续传检测、MD5检测
        /// </summary>
        /// <returns></returns>
        private UploaderResult ProcessCheck(HttpRequestBase request, string savepath = null
            , Func<string, string> setfilename = null, Func<string, bool> md5check = null)
        {
            UploaderResult obj = new UploaderResult();

            //文件保存路径
            string path = string.IsNullOrWhiteSpace(savepath) ? AppDomain.CurrentDomain.BaseDirectory 
                + "WebFiles\\" : savepath;

            //文件名称
            string filename = request.Form["filename"] == null ? null : request.Form["filename"].ToString();

            //文件ID
            string id = request.Form["id"] == null ? null : request.Form["id"].ToString();

            //文件大小
            long size = request.Form["size"] == null ? 0 : Convert.ToInt64(request.Form["size"]);

            //文件分块大小
            long chunksize = request.Form["chunksize"] == null ? 0 : Convert.ToInt64(request.Form["chunksize"]);

            //文件的md5
            string md5 = request.Form["md5"] == null ? null : request.Form["md5"].ToString();

            //文件区块总数
            int chunks = chunksize != 0 ? Convert.ToInt32(size / chunksize) : 1;

            //最终保存名称
            string savename = setfilename == null ? filename : setfilename(filename) == "" ? filename : setfilename(filename);

            //md5检测
            if (md5check(md5))
            {
                obj.Filish = true;
                obj.Message = "文件已存在于服务器、跳过上传";
                obj.FileExtist = true;//表明文件已存在于服务器
                return obj;
            }
            UploadFileHeadData headData = GetHeadData(path + savename + UploadFileHeadData.FILEEXT);
            int j = 0;
            if (!headData.Instance)
            {
                for (int i = 0; i < chunks; i++)
                {
                    if (!headData.UploadEmptyChunks.Contains(i))
                    {
                        obj.ChunkNum.Add(i);//服务器已经存在的区块编号
                        j++;
                    }
                }
            }
            obj.Message = string.Format("服务器已经存在区块数量{0},总区块数量{1},占比{2}%", j
                , chunks, chunks != 0 && j != 0 ? Convert.ToDouble(Convert.ToDouble(j) / Convert.ToDouble(chunks))*100 : 0);
            return obj;
        }
    }


}
